package cn.xcyy.itcc.service.impl;

import cn.xcyy.itcc.HighlightResultMapper;
import cn.xcyy.itcc.doc.CourseDoc;
import cn.xcyy.itcc.dto.CourseSearchDto;
import cn.xcyy.itcc.repository.SearchRepository;
import cn.xcyy.itcc.result.EsAggrPageList;
import cn.xcyy.itcc.result.PageList;
import cn.xcyy.itcc.service.ISearchServicce;
import cn.xcyy.itcc.vo.AggrTermsBucketVo;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchService;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class SearchServicceImpl implements ISearchServicce {

    @Autowired
    private SearchRepository repository;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Autowired
    private HighlightResultMapper highlightResultMapper;




    @Override
    public void courseEsAdd(CourseDoc courseDoc) {
        repository.save(courseDoc);
    }

    @Override
    public PageList<CourseSearchDto> search(CourseSearchDto courseSearchDto) {
        // 1.得到条件构造器
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        // 2.设置分页条件
        nativeSearchQueryBuilder.withPageable(PageRequest.of(courseSearchDto.getPage() - 1, courseSearchDto.getRows()));

        // 3.设置排序，必须排序字段不为空才说明要排序
        if (!StringUtils.isEmpty(courseSearchDto.getSortField()) && !StringUtils.isEmpty(courseSearchDto.getSortType())) {
            // 3.1.判断升序or降序，得到枚举对象
            SortOrder sortOrder = courseSearchDto.getSortType().equalsIgnoreCase("ASC") ? SortOrder.ASC : SortOrder.DESC;
            // 3.2.判断是要根据那个字段排序
            String fieldSort = null;
            switch (courseSearchDto.getSortField().toLowerCase()) {
                case "xl":
                    fieldSort = "saleCount";
                    break;
                case "xp":
                    fieldSort = "onlineTime";
                    break;
                case "pl":
                    fieldSort = "commentCount";
                    break;
                case "jg":
                    fieldSort = "price";
                    break;
                case "rq":
                    fieldSort = "viewCount";
                    break;
                default:
                    break;
            }
            if (fieldSort != null) {
                nativeSearchQueryBuilder.withSort(new FieldSortBuilder(fieldSort).order(sortOrder));
            }
        }

        // 4.设置条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 4.1.设置DSL查询，模糊查询课程名称
        if (!StringUtils.isEmpty(courseSearchDto.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("name", courseSearchDto.getKeyword()));
        }
        // 4.2.设置DSL过滤查询 courseTypeId、gradeName、chargeName、priceMin、priceMax
        if (!StringUtils.isEmpty(courseSearchDto.getCourseTypeId().toString())) {
            boolQuery.filter(QueryBuilders.termQuery("courseTypeId", courseSearchDto.getCourseTypeId()));
        }
        if (!StringUtils.isEmpty(courseSearchDto.getGradeName())) {
            boolQuery.filter(QueryBuilders.termQuery("gradeName", courseSearchDto.getGradeName()));
        }
        if (!StringUtils.isEmpty(courseSearchDto.getChargeName())) {
            boolQuery.filter(QueryBuilders.termQuery("charge", courseSearchDto.getChargeName()));
        }
        if (!StringUtils.isEmpty(courseSearchDto.getPriceMin().toString())) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(courseSearchDto.getPriceMin()));
        }
        if (!StringUtils.isEmpty(courseSearchDto.getPriceMax().toString())) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").lte(courseSearchDto.getPriceMax()));
        }

        // 5.将条件对象放入到搜索构造器中
        nativeSearchQueryBuilder.withQuery(boolQuery);

        // 6.高亮关键字查询字段name中的值
        nativeSearchQueryBuilder.withHighlightFields(new HighlightBuilder.Field("name")
                .preTags("<font style='color:red'>").postTags("</font>"));

        // 7.聚合查询
        // 7.1.设置需要聚合查询的字段
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("gradeNameTermsAgg").field("gradeName"))
                .addAggregation(AggregationBuilders.terms("chargeNameTermsAgg").field("charge"));

        // 8.通过搜索对象构造器得到搜索对象，我们的条件都在条件对象中
        NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
        // 9.根据查询条件查询ES数据
        // Page<CourseDoc> page = courseEsRepository.search(nativeSearchQuery);
        //Page<CourseDoc> page = elasticsearchRestTemplate.queryForPage(nativeSearchQuery, CourseDoc.class, highlightResultMapper);
        AggregatedPage<CourseSearchDto> aggregatedPage = elasticsearchRestTemplate.queryForPage(nativeSearchQuery, CourseSearchDto.class, highlightResultMapper);

        // 10.封装聚合结果
        // 10.1.得到聚合结果对象
        Aggregations aggregations = aggregatedPage.getAggregations();
        // 10.2.创建最终要响应的数据格式对象
        Map<String, List<AggrTermsBucketVo>> aggreResultMap = new HashMap<>();
        // 10.3.遍历聚合结果，封装数据到响应对象中
        aggregations.getAsMap().entrySet().forEach(entry -> {
            // 1.将聚合结果进行对象转换，因为ParsedStringTerms中才有方法让我们可以获取到聚合结果
            ParsedStringTerms parsedStringTerms = (ParsedStringTerms) entry.getValue();
            // 2.创建一个list集合，用于内层循环存放数据
            ArrayList<AggrTermsBucketVo> aggrTermsBuckets = new ArrayList<>();
            // 3.遍历此字段聚合结果bucket对象
            parsedStringTerms.getBuckets().forEach(bucket -> {
                // 3.1.将聚合值名称和此名称对应数量放入到我们封装的对象中，再将对象放入到集合中
                aggrTermsBuckets.add(new AggrTermsBucketVo(bucket.getKeyAsString(), bucket.getDocCount()));
            });
            // 4.内层循环结束将集合数据存入到map中，key就是此聚合结果我们起的别名，value就是我们组装的聚合结果
            aggreResultMap.put(entry.getKey(), aggrTermsBuckets);
        });

        // 8.封装分页对象进行响应
        return new EsAggrPageList<>(aggregatedPage.getTotalElements(),aggregatedPage.getContent(),aggreResultMap);
    }


}
